package local.kexnichi.appengine.gcalfinance.servlet;

import java.io.IOException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import local.kexnichi.appengine.gcalfinance.utility.Constants;
import local.kexnichi.appengine.gcalfinance.utility.Utility;

import com.google.api.client.auth.oauth2.AuthorizationCodeFlow;
import com.google.api.client.extensions.appengine.auth.oauth2.AbstractAppEngineAuthorizationCodeServlet;
import com.google.api.client.util.DateTime;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.Gson;

public class MainServlet extends AbstractAppEngineAuthorizationCodeServlet {

    private static final long serialVersionUID = -8594272398319172015L;
    // ログ出力用
    private static final Logger log = Logger.getLogger(MainServlet.class
            .getName());

    @Override
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws IOException, ServletException {

        // ユーザサービス
        UserService userService = UserServiceFactory.getUserService();
        // 読み込み
        com.google.api.services.calendar.Calendar baseCalendar = Utility
                .loadCalendarClient();

        // パラメータ解析
        Map<String, String[]> queryMap = request.getParameterMap();
        boolean isAll = false;
        String[] calendarIdQueryMapArray = queryMap.get("calendarid");

        // カレンダーリストの取得
        List<Object[]> calendarList = getCalendarList(baseCalendar,
                calendarIdQueryMapArray);
        // イベントリストの取得
        Map<String, List<Object[]>> eventList = getEventList2(baseCalendar,
                calendarList);

        // HTML生成時に必要なものをそろえる
        Calendar cal = Calendar.getInstance();
        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 1);
        String thisMonth = Utility.getYYYYMMDD(cal);
        StringBuilder calendarids = new StringBuilder();
        if (calendarIdQueryMapArray != null) {
            Utility.createParamString(calendarids, calendarIdQueryMapArray,
                    "calendarid");
        }
        // TODO カレンダーの色も対応したい
        String[] googleCalendarSrcArray = new String[calendarList.size()];
        int idx = 0;
        for (Object[] obj : calendarList) {
            googleCalendarSrcArray[idx++] = (String) obj[Constants.CALENDAR_ID];
        }
        StringBuilder googleCalendarSrcs = new StringBuilder();
        Utility.createParamString(googleCalendarSrcs, googleCalendarSrcArray,
                "src");
        Gson gson = new Gson();
        StringBuilder script = new StringBuilder("\n");
        script.append("var calendarList = ").append(gson.toJson(calendarList))
                .append(";\n");
        script.append("var eventList = ").append(gson.toJson(eventList))
                .append(";\n");
        script.append("var googleCalendarSrcs = '")
                .append(Constants.GOOGLE_CALENDAR_BASE_URL)
                .append(googleCalendarSrcs).append("';\n");
        script.append("var thisMonth = '").append(thisMonth).append("';\n");

        // レスポンス内容の構築
        response.setContentType("text/html");
        response.setCharacterEncoding("UTF-8");
        StringBuilder c = new StringBuilder();

        c.append("<!DOCTYPE html>");
        c.append("<html><head>");
        c.append("<meta charset=\"UTF-8\" />");
        c.append("<title>GCalFinance</title>");
        c.append("<meta name=\"viewport\" content=\"initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0\" />");
        c.append("<meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />");
        c.append("<link rel=\"apple-touch-icon\" href=\"/img/icon.png\" />");
        c.append("<link rel=\"shortcut icon\" href=\"/img/favicon.ico\" />");
        c.append("<link rel=\"stylesheet\" href=\"//ajax.aspnetcdn.com/ajax/jquery.mobile/1.2.0/jquery.mobile-1.2.0.min.css\" />");
        c.append("<link rel=\"stylesheet\" href=\"/css/main.min.css\" />");
        c.append("<script src=\"//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js\"></script>");
        c.append("<script>").append(script).append("</script>"); // TODO 削除すること
        c.append("<script src=\"//www.google.com/jsapi\"></script>");
        // c.append("<script src=\"/js/main.min.js?ver=")
        c.append("<script src=\"/js/main.js?ver=")
                .append(Constants.CACHE_BURST).append("\"></script>");
        c.append("<script src=\"//ajax.aspnetcdn.com/ajax/jquery.mobile/1.2.0/jquery.mobile-1.2.0.min.js\"></script>");
        c.append("</head><body>");

        // 月の数だけページを生成する
        Object[] monthList = eventList.keySet().toArray();
        for (int i = 0, l = monthList.length; i < l; i++) {
            String pdate;
            String date = (String) monthList[i];
            String ndate;
            c.append("<div data-role=\"page\" id=\"main").append(date)
                    .append("\" data-fullscreen=\"true\">");
            c.append("<header id=\"main")
                    .append(date)
                    .append("_header\" data-role=\"header\" data-position=\"fixed\" data-tap-toggle=\"false\" >");
            if (i > 0) {
                pdate = (String) monthList[i - 1];
                c.append("<a href=\"#main")
                        .append(pdate)
                        .append("\" data-icon=\"arrow-l\" data-transition=\"slide\" data-direction=\"reverse\" >prev</a>");
            }
            c.append("<h1>").append(date).append("</h1>");
            if (i < l - 1) {
                ndate = (String) monthList[i + 1];
                c.append("<a href=\"#main")
                        .append(ndate)
                        .append("\" data-icon=\"arrow-r\" data-iconpos=\"right\" class=\"ui-btn-right\" data-transition=\"slide\" >next</a>");
            }
            c.append("</header>");
            c.append("<div id=\"main")
                    .append(date)
                    .append("_content\" data-role=\"content\" style=\"margin:45px 0px 40px 0px;\">");
            c.append("<div id=\"main").append(date)
                    .append("_sum_money\"></div>");
            c.append("<div id=\"main").append(date)
                    .append("_category_pie\"></div>");
            c.append("<div id=\"main").append(date)
                    .append("_budget_bar\"></div>");
            c.append("<div id=\"main").append(date)
                    .append("_daily_column\"></div>");
            c.append("<div id=\"main").append(date).append("_table\"></div>");
            c.append("</div>");
            c.append("<footer id=\"main")
                    .append(date)
                    .append("_footer\" data-role=\"footer\" data-position=\"fixed\" data-tap-toggle=\"false\" >");
            c.append("<div data-role=\"navbar\">");
            c.append("<ul>");
            c.append("<li><a href=\"#all\">All</a></li>");
            c.append("<li><a href=\"#main").append(thisMonth)
                    .append("\" class=\"ui-btn-active\">Monthly</a></li>");
            c.append("<li><a href=\"#cal\">Calendar</a></li>");
            c.append("<li><a href=\"")
                    .append(userService.createLogoutURL(Constants.INDEX_URI
                            + calendarids.toString()))
                    .append("\" rel=\"external\">Logout</a></li>");
            c.append("</ul>");
            c.append("</div>");
            c.append("</footer>");
            c.append("</div>");
        }

        // カレンダーを表示するページ
        c.append("<div data-role=\"page\" id=\"cal\" data-add-back-btn=\"true\" data-fullscreen=\"true\">");
        c.append("<header id=\"cal_header\" data-role=\"header\" data-position=\"fixed\" data-tap-toggle=\"false\">");
        c.append("<h6>Calendar</h6>");
        c.append("</header>");
        c.append("<div id=\"cal_content\" data-role=\"content\" style=\"margin:45px 0px 40px 0px;\">");
        c.append("<iframe id=\"cal_iframe\" style=\"border: 0\" width=\"100%\" frameborder=\"0\" scrolling=\"no\"></iframe>");
        c.append("</div>");
        c.append("<footer id=\"cal_footer\" data-role=\"footer\" data-position=\"fixed\" data-tap-toggle=\"false\">");
        c.append("<div data-role=\"navbar\">");
        c.append("<ul>");
        c.append("<li><a href=\"#all\">All</a></li>");
        c.append("<li><a href=\"#main").append(thisMonth)
                .append("\" >Monthly</a></li>");
        c.append("<li><a href=\"#cal\" class=\"ui-btn-active\">Calendar</a></li>");
        c.append("<li><a href=\"")
                .append(userService.createLogoutURL(Constants.INDEX_URI
                        + calendarids.toString()))
                .append("\" rel=\"external\">Logout</a></li>");
        c.append("</ul>");
        c.append("</div>");
        c.append("</footer>");
        c.append("</div>");

        // Allのページ
        c.append("<div data-role=\"page\" id=\"all\" data-add-back-btn=\"true\" data-fullscreen=\"true\">");
        c.append("<header id=\"all_header\" data-role=\"header\" data-position=\"fixed\" data-tap-toggle=\"false\">");
        c.append("<h6>All</h6>");
        c.append("</header>");
        c.append("<div id=\"all_content\" data-role=\"content\" style=\"margin:45px 0px 40px 0px;\">");
        // c.append("<div id=\"all_sum_money\"></div>");
        c.append("<div id=\"all_category_pie\"></div>");
        // c.append("<div id=\"main_budget_bar\"></div>");
        // c.append("<div id=\"main_daily_column\"></div>");
        c.append("<div id=\"all_table\"></div>");
        c.append("</div>");
        c.append("<footer id=\"all_footer\" data-role=\"footer\" data-position=\"fixed\" data-tap-toggle=\"false\">");
        c.append("<div data-role=\"navbar\">");
        c.append("<ul>");
        c.append("<li><a href=\"#all\" class=\"ui-btn-active\" >All</a></li>");
        c.append("<li><a href=\"#main").append(thisMonth)
                .append("\" >Monthly</a></li>");
        c.append("<li><a href=\"#cal\">Calendar</a></li>");
        c.append("<li><a href=\"")
                .append(userService.createLogoutURL(Constants.INDEX_URI
                        + calendarids.toString()))
                .append("\" rel=\"external\">Logout</a></li>");
        c.append("</ul>");
        c.append("</div>");
        c.append("</footer>");
        c.append("</div>");

        // 隠し情報
        c.append("<form name=\"hidden\">");
        c.append("<input type=\"hidden\" name=\"calendarList\" value=\""
                + Utility.htmlEncode(gson.toJson(calendarList)) + "\"/>");
        c.append("<input type=\"hidden\" name=\"eventList\" value=\""
                + Utility.htmlEncode(gson.toJson(eventList)) + "\"/>");
        c.append("<input type=\"hidden\" name=\"googleCalendarSrcs\" value=\""
                + Utility.htmlEncode(Constants.GOOGLE_CALENDAR_BASE_URL
                        + googleCalendarSrcs) + "\"/>");
        c.append("<input type=\"hidden\" name=\"thisMonth\" value=\""
                + Utility.htmlEncode(thisMonth) + "\"/>");
        c.append("</form>");
        c.append("</body></html>");

        // 描画する
        response.getWriter().print(c.toString());
    }

    @Override
    protected AuthorizationCodeFlow initializeFlow() throws ServletException,
            IOException {
        return Utility.initializeFlow();
    }

    @Override
    protected String getRedirectUri(HttpServletRequest request)
            throws ServletException, IOException {
        return Utility.getRedirectUri(request);
    }

    /**
     * カレンダーの取得
     * 
     * @param cal
     * @param categoryIds
     * @return
     * @throws IOException
     */
    private List<Object[]> getCalendarList(
            com.google.api.services.calendar.Calendar cal, String[] categoryIds)
            throws IOException {
        List<Object[]> calendarList = Lists.newArrayList();
        Object[] calendar = new Object[4];
        com.google.api.services.calendar.model.CalendarList cl = cal
                .calendarList().list().execute();

        while (true) {
            List<com.google.api.services.calendar.model.CalendarListEntry> clel = cl
                    .getItems();
            if (clel != null) {
                for (com.google.api.services.calendar.model.CalendarListEntry cle : clel) {
                    // カレンダー名の取得
                    String id = cle.getId();
                    String summary = cle.getSummary();
                    summary = summary == null ? "" : summary;
                    // 対象のカレンダーでなければ処理をスキップ
                    if (categoryIds != null) {
                        if (!Arrays.asList(categoryIds).contains(id)) {
                            continue;
                        }
                    } else if (summary.indexOf(Constants.BASE_CALENDAR_NAME) != 0) {
                        continue;
                    }
                    // カレンダーの色を取得
                    String calorId = cle.getColorId();
                    calorId = calorId == null ? "" : calorId;
                    // カレンダーのLocation（予算）を取得
                    int budget = 0;
                    try {
                        budget = Integer.parseInt(cle.getLocation());
                    } catch (NumberFormatException e) {
                        budget = 0;
                    }

                    // リストに追加
                    calendar = new Object[] { id, summary, calorId, budget };
                    calendarList.add(calendar);
                }
            }
            String token = cl.getNextPageToken();
            if (token != null && !token.isEmpty()) {
                cl = cal.calendarList().list().setPageToken(token).execute();
            } else {
                break;
            }
        }

        return calendarList;
    }

    /**
     * イベントの取得
     * 
     * @param baseCalendar
     * @param calendarList
     * @return
     * @throws IOException
     */
    private Map<String, List<Object[]>> getEventList2(
            com.google.api.services.calendar.Calendar baseCalendar,
            List<Object[]> calendarList) throws IOException {

        Map<String, List<Object[]>> eventList = Maps.newTreeMap(); // 月の順に並べたいのでTreeMapを利用する
        List<Object[]> eventListMonth;

        // カレンダーの数だけループ処理
        for (Object[] calendar : calendarList) {
            String calendarId = (String) calendar[Constants.CALENDAR_ID];
            String categoty = (String) calendar[Constants.CALENDAR_SUMMARY];
            com.google.api.services.calendar.model.Events events = baseCalendar
                    .events().list(calendarId).execute();
            while (true) {
                List<com.google.api.services.calendar.model.Event> el = events
                        .getItems();
                if (el != null) {
                    for (com.google.api.services.calendar.model.Event ev : el) {
                        // 日付を取得
                        DateTime dateTime = ev.getStart().getDate();
                        dateTime = dateTime == null ? ev.getStart()
                                .getDateTime() : dateTime;
                        String date = dateTime.toStringRfc3339().substring(0,
                                10);
                        String month = date.substring(0, 7) + "-01";
                        // イベントのタイトル(金額)を取得
                        int money = 0;
                        try {
                            money = Integer.parseInt(ev.getSummary());
                        } catch (NumberFormatException e) {
                            e.printStackTrace();
                        }
                        // 備考を取得
                        String description = ev.getDescription();
                        description = description == null ? "" : description;

                        // リストに追加
                        Object[] obj = { date, categoty, money, description };
                        if (eventList.containsKey(month)) {
                            eventList.get(month).add(obj);
                        } else {
                            eventListMonth = Lists.newArrayList();
                            eventListMonth.add(obj);
                            eventList.put(month, eventListMonth);
                        }
                    }
                }
                String token = events.getNextPageToken();
                if (token != null && !token.isEmpty()) {
                    events = baseCalendar.events().list(calendarId)
                            .setPageToken(token).execute();
                } else {
                    break;
                }
            }
        }

        // イベントリストのソート
        for (String month : eventList.keySet()) {
            sortEventList(eventList.get(month));
        }

        return eventList;
    }

    /**
     * イベントリストから指定した日付でデータを抽出する
     * 
     * @param eventList
     * @param startDate
     * @param endDate
     * @return
     */
    private List<Object[]> refineEventList(List<Object[]> eventList,
            String startDate, String endDate) {
        final String start = startDate;
        final String end = endDate;
        return Lists.newArrayList(Collections2.filter(eventList,
                Predicates.and(new Predicate<Object[]>() {
                    public boolean apply(Object[] event) {
                        String date = (String) event[Constants.EVENT_DATE];
                        return date.compareTo(start) >= 0;
                    }
                }, new Predicate<Object[]>() {
                    public boolean apply(Object[] event) {
                        String date = (String) event[Constants.EVENT_DATE];
                        return date.compareTo(end) < 0;
                    }
                })));
    }

    /**
     * イベントリストを日付でソートする
     * 
     * @param eventList
     */
    private void sortEventList(List<Object[]> eventList) {
        Collections.sort(eventList, new Comparator<Object[]>() {
            public int compare(Object[] event1, Object[] event2) {
                String date1 = (String) event1[Constants.EVENT_DATE];
                String date2 = (String) event2[Constants.EVENT_DATE];
                int ret = 0;
                // dateの昇順
                if ((ret = date1.compareTo(date2)) == 0) {
                    // dateが同じ場合はcategoryの昇順
                    String category1 = (String) event1[Constants.EVENT_CATEGORY];
                    String category2 = (String) event2[Constants.EVENT_CATEGORY];
                    ret = category1.compareTo(category2);
                }
                return ret;
            }
        });
    }
}
